<?php
namespace app\library\Points;


Class Points extends Db{
    
    //保留积分类型
    protected $def_types = [
        //过期
        'overdue' => 999000,
        //由积分模块发生的奖励类型
        'add' => 999001,
        //由积分模块发生的扣除积分类型
        'reduce' => 999002,
        //退款订单类型
        'refund' => 999003,
        //当扣减积分不够减，触发自动补全时，添加积分类型
        'autoadd' => 999004,
    ];

    /**
     * 是否是保留积分类型
     */
    public function isDefType($type){
        return in_array($type, $this->def_types);
    }
    
    public function getOrder($order_num){
        $sql = "select * from points_log where order_num='{$order_num}'";
        return $this->queryOne($sql);
    }
    
    /**
     * 订单是否存在
     * @param str $order_num
     */
    public function orderExists($order_num){
        return !empty($this->getOrder($order_num));
    }

    /**
     * 添加积分
     * 
     * @param int $pid 项目id
     * @param int $mid 会员id
     * @param int $points 积分
     * @param str $order_num 订单号
     * @param str $remark 描述
     * @param int $type 类型
     * @param int $create_time 创建时间，默认当前时间
     * @param int $end_time 如果是过期积分，则设置过期时间，默认不过期
     */
    function add($pid, $mid, $points, $order_num, $remark, $type, $create_time = 0, $end_time = 0){
        if(!$this->isself){
            $this->init();
        }
        if( !$this->isself && $this->isDefType($type) ){
            $this->setErr("积分类型不能为保留类型");
            return false;
        }
        
        $this->beginTransaction();
        try{
            //判断订单号是否重复
            if( $this->orderExists($order_num) ){
                $this->setErr("订单编号重复");
                return false;
            }
            if(!$create_time){
                $create_time = time();
            }

            $data = [
                'pid' => $pid,
                'mid' => $mid,
                'order_num' => $order_num,
                'points' => $points,
                'last' => $points,
                'create_time' => $create_time,
                'end_time' => $end_time,
                'update_time' => time(),
            ];
            $id = $this->insert('points_add', $data);
            $data = [
                'pid' => $pid,
                'mid' => $mid,
                'order_num' => $order_num,
                'kid' => $id,
                'points' => $points,
                'isadd' => 1,
                'type' => $type,
                'remark' => $remark,
                'refund_time' => 0,
                'create_time' => $create_time,
                'trigger_time' => 0,
                'trigger_result1' => '',
                'trigger_result2' => '',
            ];
            
            $data['id'] = $this->insert('points_log', $data);
            $this->trigger($data);
        } catch (\Exception $ex) {
            $this->rollBack();
            $this->setErr(strval($ex));
            return false;
        }
        
        if(!$this->isself){//外部调用才记录冗余
            $this->redundancy($pid, $mid, true, $points);
        }
        
        $this->commit();
        return true;
    }
    
    /**
     * 减少积分
     * 
     * @param int $pid 项目id
     * @param int $mid 会员id
     * @param int $points 积分
     * @param str $order_num 订单号
     * @param str $remark 描述
     * @param int $type 类型
     * @param int $create_time 创建时间，默认当前时间
     * @param bool $autoadd 如果不够减，是否自动补全
     */
    function reduce($pid, $mid, $points, $order_num, $remark, $type, $create_time = 0, $autoadd=false){
        if(!$this->isself){
            $this->init();
        }
        
        //外部调用时，判定类型
        if( !$this->isself && $this->isDefType($type) ){
            $this->setErr("积分类型不能为保留类型");
            return false;
        }
        
        $this->beginTransaction();
        try{
            //判断订单号是否重复
            if( $this->orderExists($order_num) ){
                $this->setErr("订单编号重复");
                return false;
            }
//            \app\library\PublicFunction::testUnit();
            $now = time();
            $sql = "select * from points_add where pid='{$pid}' and mid='{$mid}' and last > 0 and (end_time > '{$now}' OR end_time = '0') order by end_time asc";
            $add_points = $this->queryAll($sql);
            $total = $points;
            $used = [];
//            echo $sql;exit;
//            \app\library\PublicFunction::testUnit();
//            exit;
//            \app\library\PublicFunction::testUnit();
            foreach($add_points as $add_point){
                $last = $add_point['last'] - $total;
                
                if($last >= 0){
                    $used[$add_point['id']] = [
                        'used' => $total,
                        'last' => $last,
                    ];
                    
                    $total = 0;
                    break;
                }else{
                    $total = $total - $add_point['last'];
                    
                    $used[$add_point['id']] = [
                        'used' => $add_point['last'],
                        'last' => 0,
                    ];
                }
            }
//            \app\library\PublicFunction::testUnit();
           
            if($total == 0){//消费成功
                if(!$create_time){
                    $create_time = time();
                }
                
                $data = [
                    'pid' => $pid,
                    'mid' => $mid,
                    'order_num' => $order_num,
                    'points' => $points,
                    'create_time' => $create_time,
                ];
                $id = $this->insert('points_reduce', $data);
//            \app\library\PublicFunction::testUnit();
                foreach($used as $aid => $item){
                    $relation = [
                        'a_id' => $aid,
                        'r_id' => $id,
                        'points' => $item['used'],
                    ];
                    $this->insert('points_relation', $relation);
                    $add_data = [
                        'last' => $item['last'],
                        'update_time' => $now,
                    ];
                    $this->update('points_add', $add_data, ['id'=>$aid]);
                }
//            \app\library\PublicFunction::testUnit();
                $points_log = [
                    'pid' => $pid,
                    'mid' => $mid,
                    'order_num' => $order_num,
                    'kid' => $id,
                    'points' => $points,
                    'isadd' => 0,
                    'type' => $type,
                    'remark' => $remark,
                    'refund_time' => 0,
                    'create_time' => $create_time,
                    'trigger_time' => 0,
                    'trigger_result1' => '',
                    'trigger_result2' => '',
                ];

                $points_log['id'] = $this->insert('points_log', $points_log);
//            \app\library\PublicFunction::testUnit();
                $this->trigger($points_log);
//            \app\library\PublicFunction::testUnit();
            }else{
                if($autoadd){
                    $this->isself = true;
                    $res = $this->add($pid, $mid, $total, uniqid(), '系统调整', $this->def_types['autoadd'], $create_time);
                    //补冗余
                    $this->redundancy($pid, $mid, 1, $total);
                    $this->isself = false;
                    return $this->reduce($pid, $mid, $points, $order_num, $remark, $type, $create_time, false);
                }
                $this->rollBack();
                $this->setErr("积分不足");
                return false;
            }
            
        } catch (\Exception $ex){
            $this->rollBack();
            $this->setErr(strval($ex));
            return false;
        }
        
        if(!$this->isself){//外部调用才记录冗余
            $this->redundancy($pid, $mid, false, $points);
        }
        $this->commit();
        return true;
    }
    
    /**
     * 退单
     * 
     * @param int $pid 项目ID
     * @param int $mid 会员id
     * @param str $refund_order_num 退单单号
     * @param str $remark 退单描述
     * @param int $create_time 退单时间，默认当前时间
     * @return boolean
     */
    function refund($pid, $mid, $refund_order_num, $remark, $create_time = 0){
        $this->init();
        
        $this->beginTransaction();
        try{
            //获取退款订单
            $sql = "select * from points_log where order_num='{$refund_order_num}' for update";
            $points_log = $this->queryOne($sql);
            if(!$points_log){
                $this->setErr("退款订单不存在");
                return false;
            }
            
            if($this->isDefType($points_log['type'])){
                $this->setErr("保留类型订单不可退");
                return false;
            }
            
            if($points_log['refund_time'] > 0){
                $this->setErr("已退订单不能再次退");
                return false;
            }
            
            if($points_log['isadd']){#//退添加积分单
                $this->isself = true;
                $res = $this->reduce($pid, $mid, $points_log['points'], uniqid('refund_'), $remark, $this->def_types['refund'], $create_time);
                $this->isself = false;
                if(!$res){
                    $this->setErr('退款失败，积分不足');
                }else{
                    //冗余退加积分
                    $this->redundancy($pid, $mid, true, $points_log['points'], true);
                }
            }else{//退减少积分单
                $sql = "select * from points_relation where r_id = '{$points_log['kid']}'";
                $relations = $this->queryAll($sql);
                
                $now = time();
                $overdue = 0;
                foreach($relations as $relation){
                    $sql = "select * from points_add where id='{$relation['a_id']}'";
                    $add_log = $this->queryOne($sql);
                    $update = [
                        'last' => $add_log['ast'] + $relation['points'],
                        'update_time' => time(),
                    ];
                    
                    $this->update('points_add', $update, ['id'=>$relation['a_id']]);
                    
                    if($add_log['overdued']){//如果已经过期，此处用标识位判断，不能用end_time判断
                        $overdue += $relation['points'];
                    }
                }
                
                if($overdue){//存在过期积分，则过期该积分
                    $new = [
                        'pid' => $pid,
                        'mid' => $mid,
                        'order_num' => uniqid('overdue_'),
                        'kid' => 0,
                        'points' => $overdue,
                        'isadd' => 0,
                        'type' => $this->def_types['overdue'],
                        'remark' => '退单过期',
                        'refund_time' => 0,
                        'create_time' => $create_time,
                        'trigger_time' => 0,
                        'trigger_result1' => '',
                        'trigger_result2' => '',
                    ];
                    
                    $this->insert('points_log', $new);
                }
                
                //添加退单日志
                $new = [
                    'pid' => $pid,
                    'mid' => $mid,
                    'order_num' => uniqid('refund_'),
                    'kid' => 0,
                    'points' => $points_log['points'],
                    'isadd' => 0,
                    'type' => $this->def_types['refund'],
                    'remark' => $remark,
                    'refund_time' => 0,
                    'create_time' => $create_time,
                    'trigger_time' => 0,
                    'trigger_result1' => '',
                    'trigger_result2' => '',
                ];
                $this->insert('points_log', $new);
                
                $res = true;
            }
            
            if($res){//退款成功则设置该订单已经退
                $update = [
                    'refund_time' => time()
                ];
                
                $this->update('points_log', $update, ['id' => $points_log['id']]);
                //冗余退减积分
                $this->redundancy($pid, $mid, false, $points_log['points'], true);
            }
            
        } catch (\Exception $ex){
            $this->rollBack();
            $this->setErr(strval($ex));
            return false;
        }
        
        $this->commit();
        return $res;
    }
    
    /**
     * 过期积分
     */
    function overdue(){
        $now = time();
        $sql = "select gid, mid, sum(last) total from points_add where overdued=0 and (end_time < '{$now}' OR end_time = '0') group by gid, mid";
        $all = $this->queryAll($sql);
        foreach($all as $item){
            //添加退单日志
            $new = [
                'pid' => $item['pid'],
                'mid' => $item['mid'],
                'order_num' => uniqid('overdue_'),
                'kid' => 0,
                'points' => $item['total'],
                'isadd' => 0,
                'type' => $this->def_types['overdue'],
                'remark' => '过期',
                'refund_time' => 0,
                'create_time' => $now,
                'trigger_time' => 0,
                'trigger_result1' => '',
                'trigger_result2' => '',
            ];
            
            $this->insert('points_log', $new);
        }
        
        $update = [
            'overdued' => 1,
        ];
        $this->update('points_add', $update, "overdued=0 and (end_time < '{$now}' OR end_time = '0')");
    }
    
    
    
    /**
     * 获取冗余
     * @param int $mid 会员id
     */
    function getRedundancy($pid, $mid){
        $this->init();
        
        $sql = "select * from points_redundancy where pid='{$pid}' and mid = '{$mid}'";
        $res = $this->queryOne($sql);
        if(!$res){
            return [
                'points' => 0,
                'total_points' => 0,
                'total_reduce' => 0,
            ];
        }
        return $res;
    }
    
    /**
     * 获取会员积分日志
     * @param type $mid
     */
    function getLogs($pid, $mid, $page, $pagesize = 0){
        $this->init();
        
        $page = int($page);
        $page = $page < 1 ? 1 : 0;
        $pagesize = $pagesize ? $pagesize : $this->pagesize;
        $offset = $pagesize * ($page - 1);
        $sql = "select SQL_CALC_FOUND_ROWS * from points_log where gid = '{$gid}' and mid='{$mid}' limit {$offset}, {$pagesize} order by create_time asc";
        $list = $this->queryAll($sql);
        $sql = "SELECT FOUND_ROWS() total";
        $res = $this->queryOne($sql);
        $total = $res['total'];
        
        return [
            'list' => $list,
            'total' => $total,
        ];
    }
    
    /**
     * 记录融于
     * @param int $mid 会员id
     * @param bool $isadd 是否为加积分
     * @param int $points 积分
     * @param bool $isrefund 是否为退单，$isadd = true && $isrefund = true 退增加积分，$isadd = false && $isrefund = true：退减少积分订单
     */
    protected function redundancy($pid, $mid, $isadd, $points, $isrefund=false){
        static $a=0, $b=0;
        $sql1 = '';
        if($isrefund){//如果是退单
            if($isadd){//退增加积分，当前积分-所退积分，总积分 - 所退积分
                $sql2 = "update points_redundancy set points=points-{$points}, total_points=total_points-{$points} where pid={$pid} and mid={$mid}";
            }else{//退减少积分，当前积分+所退积分，总消耗积分 - 所退积分
                $sql2 = "update points_redundancy set points=points+{$points}, total_reduce=total_reduce-{$points} where pid={$pid} and mid={$mid}";
            }
        }else{
            if($isadd){//增加积分，当前积分+所加积分，总积分 + 所加积分
                $sql1 = "insert into points_redundancy (pid, mid, points, total_points, total_reduce) values({$pid},{$mid}, $points, $points, 0)";
                $sql2 = "update points_redundancy set points=points+{$points}, total_points=total_points+{$points} where pid={$pid} and mid={$mid}";
                $a += $points;
                \app\library\PublicFunction::logs('test', ['+', 'points'=>$points, $a]);
                
            }else{//减少积分，当前积分-所减积分，总消耗积分 + 所减积分
                $sql2 = "update points_redundancy set points=points-{$points}, total_reduce=total_reduce+{$points} where pid={$pid} and mid={$mid}";
            
                $a -= $points;
                $b += $points;
                \app\library\PublicFunction::logs('test', ['-', 'points'=>$points, $a, $b]);
            }
        }
        
        if($sql1){
            try {
                $this->query($sql1);
            } catch (\Exception $ex) {
                $this->query($sql2);
            }
        }else{
            $this->query($sql2);
        }
        
    }
    
    /**
     * 触发积分规则
     * 
     * @param type $points_log 积分日志
     * @param type $instant 是否为即时规则，默认是
     */
    protected function trigger($points_log, $instant = true){
        if($instant){
            DoTrigger::getInstance()->instant($points_log);
        }else{
            DoTrigger::getInstance()->noninstant($points_log);
        }
        
    }
    
}